%%% bbprogrammes_server.erl - a proxy to programme information sourced from
%%%    http://www.bbc.co.uk/programmes/
%%%
%%% The MIT License
%%%
%%% Copyright (c) 2008 hellomatty@gmail.com
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a copy
%%% of this software and associated documentation files (the "Software"), to deal
%%% in the Software without restriction, including without limitation the rights
%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
%%% copies of the Software, and to permit persons to whom the Software is
%%% furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in
%%% all copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
%%% THE SOFTWARE.
%%%

-module(bbcprogrammes_server).

-behaviour(gen_server).

-import(web_helper).
-import(ets).
-include_lib("defs.hrl").
-include_lib("util.hrl").

-export([start_link/0]). 
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, 
terminate/2, code_change/3]). 
-compile(export_all).

-define(MAX_TCP, 20).

-define(BBC, "http://www.bbc.co.uk").
-define(XPATH_GET_GENRE_URIS, "//ul[@id='genres']//li/a/@href").
-define(XPATH_GET_GENRE_TITLE, "//title/text()").
-define(STR_GENRE_TITLE, "Categorised as ").
-define(XPATH_GET_PROGRAMME_URIS, "//ol[@id='brands']/li/a/@href" ).
-define(XPATH_GET_SERVICE, "//div[@id='broadcasts']/ol/li[1]/div[@class='location']//text()").
-define(XPATH_GET_TITLE, "//title/text()").
-define(STR_EPISODE_TITLE, "Programmes - ").
-define(XPATH_GET_SYNOPSIS, "//div[@id='synopsis']/p/text()").
-define(XPATH_GET_BRAND, "//div[@id='parents']/p[@id='brand']//text()").
-define(XPATH_GET_EPISODE_LASTON, "//ol[@class='episodes']/li[1]/div[@class='episode']//a[@class='url']/@href").

start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 

-record(bbcprogrammes_state, {
  revision = 1,
  episodes = dict:new(),
  genres = dict:new()
}).

init([]) ->
  update(),
%%{ok, _TRef} = timer:apply_interval(1000 * 60 * 10, gen_server, cast, [bbcprogrammes_server, update]),
  {ok, #bbcprogrammes_state{}}. 

handle_call(state, _From, State) ->
  {reply, {dict:to_list(State#bbcprogrammes_state.genres), dict:to_list(State#bbcprogrammes_state.episodes)}, State};
handle_call({episode, Id}, _From, State) ->
  {reply, dict:fetch(Id, State#bbcprogrammes_state.episodes), State};
handle_call({genre, Id}, _From, State) ->
  {reply, dict:fetch(Id, State#bbcprogrammes_state.genres), State};
handle_call(revision, _From, State) ->
  {reply, State#bbcprogrammes_state.revision, State};
handle_call({stream, song, id, Id, ServerPid}, _From, State) ->
  Episode = dict:fetch(Id, State#bbcprogrammes_state.episodes),
  Stream = iplayer:stream_for_version(Episode#episode.version),
  ?D({bbcprogrammes, stream, Episode, Stream}),
  Pid = rtmp_client:stream_audio(Stream, ServerPid),
  {reply, Pid, State}.
handle_cast(update, State) ->
  spawn(fun update/0),
  {noreply, State};
handle_cast({updated, Genres, Episodes}, State) ->
  ?D({bbcprogrammes,updating}),
  {noreply, State#bbcprogrammes_state{revision = State#bbcprogrammes_state.revision + 1, episodes = Episodes, genres = Genres}}.

handle_info(_Info, State) -> {noreply, State}. 

terminate(_Reason, _State) -> ok. 

code_change(_OldVsn, State, _Extra) -> {ok, State}. 

update() ->
  spawn(fun() ->
    ?D({bbcprogrammes,update}),
    GenreUris = all_genre_uris(),
    ?D({bbcprogrammes,update,genres,length(GenreUris)}),
    Genres = lists:append(util:safe_pmap(fun(BatchedGenreUris) ->
      util:safe_map(fun(GenreUri) ->
        {gen_server:call(id_server,{id, GenreUri}), genre_uri_to_genre(GenreUri)}
      end, BatchedGenreUris)
    end, util:batch(GenreUris, batch_size(length(GenreUris), ?MAX_TCP)))),
    ProgrammeUris = util:unique( lists:append( lists:map(fun({_Id, Genre}) ->
      Genre#genre.programme_uris
    end, Genres) ) ),
    ?D({bbcprogrammes,update,programme_uris,length(ProgrammeUris)}),
    Episodes = lists:append(util:safe_pmap(fun(BatchedProgrammeUris) ->
      util:safe_map(fun(ProgrammeUri) ->
        {gen_server:call(id_server, {id, ProgrammeUri}), programme_uri_to_episode(ProgrammeUri)}
      end, BatchedProgrammeUris)
    end, util:batch(ProgrammeUris, batch_size(length(ProgrammeUris), ?MAX_TCP)))),
    ?D({bbcprogrammes,update,episodes,length(Episodes)}),
    gen_server:cast(bbcprogrammes_server, {updated, dict:from_list(Genres), dict:from_list(Episodes)})
  end).

batch_size(ItemCount, MaxConcurrency) when ItemCount < MaxConcurrency ->
  ItemCount;
batch_size(ItemCount, MaxConcurrency) ->
  round(ItemCount / MaxConcurrency).

all_genre_uris() ->
  GenresUri = ?BBC ++ "/radio/programmes/genres",
  Uris = case web_helper:parse_xml_uri(GenresUri) of
    {error, GenresUri, Error} ->
      ?D({error,Error}),
      exit(fetch_failed);
    {ok, GenresXml} ->
      [?BBC ++ Uri || {xmlAttribute, href, _, _, _, _, _, _, Uri, _} <- web_helper:apply_xpath( ?XPATH_GET_GENRE_URIS, GenresXml )]
  end,
  Uris.

genre_uri_to_genre(GenreUri) ->
  case web_helper:parse_xml_uri(GenreUri ++ "/player") of
    {error, Error} ->
      ?D({error,GenreUri,Error}),
      exit(fetch_failed);
    {ok, GenreXml} ->
      genre(GenreUri, GenreXml)
  end.

programme_uri_to_episode(ProgrammeUri) ->
  {EpisodeUri, EpisodeXml} = case web_helper:parse_xml_uri(ProgrammeUri) of
    {error, Error} ->
      ?D({error,ProgrammeUri,Error}),
      exit({fetch_failed, Error});
    {ok, ProgrammeXml} ->
      case web_helper:apply_xpath( ?XPATH_GET_EPISODE_LASTON, ProgrammeXml ) of
        [] ->
%          ?D({bbcprogrammes,programme_uri_to_episode,episode,ProgrammeUri}),
          {ProgrammeUri, ProgrammeXml};
        [{xmlAttribute, href, _, _, _, _, _, _, EpisodeHref, _}] ->
          ChildUri = ?BBC ++ EpisodeHref,
%          ?D({bbcprogrammes,programme_uri_to_episode,brand,ProgrammeUri,episode,ChildUri}),
          case web_helper:parse_xml_uri( ChildUri ) of
            {error, Error} ->
              ?D({error,ChildUri,Error}),
              exit({fetch_failed,ChildUri});
            {ok, ChildXml} ->
              {ChildUri, ChildXml}
          end
      end
  end,
% ?D({bbcprogrammes,programme_uri_to_episode,ProgrammeUri,ok}),
  episode(ProgrammeUri, EpisodeUri, EpisodeXml).

episode(ProgrammeUri, EpisodeUri, EpisodeXml) ->
  #episode{
    uri = ProgrammeUri,
    service = web_helper:collect_text( web_helper:apply_xpath(?XPATH_GET_SERVICE, EpisodeXml) ),
    brand = web_helper:collect_text( web_helper:apply_xpath(?XPATH_GET_BRAND, EpisodeXml) ),
    title = extract_episode_title(web_helper:collect_text( web_helper:apply_xpath(?XPATH_GET_TITLE, EpisodeXml) )),
    description = web_helper:collect_text( web_helper:apply_xpath(?XPATH_GET_SYNOPSIS, EpisodeXml) ),
    version = iplayer:version_for_episode(pid_from_programme_uri(EpisodeUri))
  }.

genre(Uri, GenreXml) ->
  ProgrammeUris = [?BBC ++ ProgrammeUri || {xmlAttribute, href, _, _, _, _, _, _, ProgrammeUri, _} <- web_helper:apply_xpath( ?XPATH_GET_PROGRAMME_URIS, GenreXml )],
  #genre{
    uri = Uri,
    title = extract_genre_title(web_helper:collect_text( web_helper:apply_xpath(?XPATH_GET_GENRE_TITLE, GenreXml) ) ), 
    programme_uris = ProgrammeUris,
    episode_ids = gen_server:call(id_server, {ids, ProgrammeUris})
  }.

pid_from_programme_uri(Uri) ->
  LastComponent = string:rstr(Uri,"/"),
  string:substr(Uri, LastComponent + 1).

extract_genre_title(T) ->
  string:substr(T, string:str(T, ?STR_GENRE_TITLE) + length(?STR_GENRE_TITLE)).

extract_episode_title(T) ->
  string:substr(T, string:str(T, ?STR_EPISODE_TITLE) + length(?STR_EPISODE_TITLE)).
